package com.yk.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yk.api.system.dto.DeviceDTO;
import com.yk.api.system.dto.GroupDTO;
import com.yk.common.core.constant.CacheConstants;
import com.yk.common.core.constant.MqttConstants;
import com.yk.common.core.constant.NumberConstant;
import com.yk.common.core.domain.BasePageQuery;
import com.yk.common.core.domain.LoginUser;
import com.yk.common.core.exception.ServiceException;
import com.yk.common.core.exception.ServiceLogException;
import com.yk.common.core.utils.LoginHelper;
import com.yk.common.log.constant.LogConstants;
import com.yk.common.redis.service.RedisService;
import com.yk.system.dto.ControlDTO;
import com.yk.system.dto.MqttApiMessageDTO;
import com.yk.system.dto.RestControlDTO;
import com.yk.system.entity.*;
import com.yk.system.mapper.*;
import com.yk.system.service.DeviceRoleService;
import com.yk.system.service.DeviceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.jexl3.internal.Engine;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author lmx
 * @date 2023/10/24 9:45
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService {

    private final GatewayMapper gatewayMapper;
    private final GroupDeviceMapper groupDeviceMapper;
    private final TemplateMapper templateMapper;
    private final VariableMapper variableMapper;
    private final RedisService redisService;
    private final DeviceRoleService deviceRoleService;
    private final DeviceUserDetailsMapper deviceUserDetailsMapper;
    @Value("${mqtt.url}")
    private String url;
    @Value("${mqtt.username}")
    private String username;
    @Value("${mqtt.password}")
    private String password;

    @Override
    public int updateBatch(List<Device> list) {
        return baseMapper.updateBatch(list);
    }

    @Override
    public int batchInsert(List<Device> list) {
        return baseMapper.batchInsert(list);
    }

    @Override
    public List<DeviceDTO> selectNoGroup2Device() {
        return baseMapper.selectNoGroup2Device(LoginHelper.getLoginUserId());
    }

    @Override
    public List<DeviceDTO> selectNoGroup2DeviceByParam(DeviceDTO param) {
        param.setCreatedBy(LoginHelper.getLoginUserId());
        return baseMapper.selectNoGroup2DeviceByParam(param);
    }

    @Override
    public List<DeviceDTO> selectRole2Device() {
        return baseMapper.selectRole2Device(LoginHelper.getLoginUserId());
    }

    @Override
    public List<DeviceDTO> selectRole2Token(String token) {
        LoginUser loginUser = LoginHelper.getLoginUser(token);
        if (Objects.isNull(loginUser)) {
            throw new ServiceException("用户未登录");
        }
        return baseMapper.selectRole2Device(loginUser.getUserId());
    }

    @Override
    public IPage<DeviceDTO> queryPage(BasePageQuery<DeviceDTO> pageParam) {
        Long userId = LoginHelper.getLoginUserId();
        pageParam.getParam().setCreatedBy(userId);
        IPage<DeviceDTO> iPage = baseMapper.queryPage(new Page<>(pageParam.getPageNum(), pageParam.getPageSize()), pageParam.getParam());
        if (CollUtil.isNotEmpty(iPage.getRecords())) {
            iPage.getRecords().forEach(it -> {
                Gateway gateway = gatewayMapper.selectById(it.getGatewayId());
                if (Objects.nonNull(gateway)) {
                    it.setGatewayName(gateway.getName());
                    it.setAddress(gateway.getAddress());
                }

                Template template = templateMapper.selectById(it.getTemplateId());
                if (Objects.nonNull(template)) {
                    it.setTemplateName(template.getModelName());
                }

                List<GroupDTO> groupDTOS = groupDeviceMapper.listByDeviceId(it.getId(), userId);
                if (CollUtil.isNotEmpty(groupDTOS)) {
                    List<String> collect = groupDTOS.stream().map(GroupDTO::getName).collect(Collectors.toList());
                    it.setGroupNameList(collect);
                }
                // 备注
                DeviceUserDetails details = deviceUserDetailsMapper.selectOne(new LambdaQueryWrapper<DeviceUserDetails>()
                        .eq(DeviceUserDetails::getDeviceId, it.getId())
                        .eq(DeviceUserDetails::getUserId, LoginHelper.getLoginUserId()));
                if (Objects.nonNull(details)){
                    it.setNotes(details.getRemarks());
                }
            });
        }
        return iPage;
    }

    @Override
    public Boolean control(ControlDTO dto) {
        Device device = baseMapper.selectById(dto.getDeviceId());
        if (Objects.isNull(device)) {
            throw new ServiceException("设备信息有误");
        }
        Gateway gateway = gatewayMapper.selectById(device.getGatewayId());
        if (Objects.isNull(gateway)) {
            throw new ServiceException("网关信息有误");
        }
        Variable variable = variableMapper.selectById(dto.getVariableId());
        if (Objects.isNull(variable)) {
            throw new ServiceException("变量信息有误");
        }
        // 设置值
        double param = Double.parseDouble(dto.getParam());
        Float writeMax = variable.getWriteMax();
        if (Objects.nonNull(writeMax) && param > writeMax) {
            throw new ServiceException("变量最大值不能超过" + writeMax);
        }
        Float writeMin = variable.getWriteMin();
        if (Objects.nonNull(writeMin) && param < writeMin) {
            throw new ServiceException("变量最小值不能低于" + writeMin);
        }
        String writeFormula = variable.getWriteFormula();
        if (NumberConstant.ZERO_STR.equals(variable.getType()) && CharSequenceUtil.isNotBlank(writeFormula)) {
            JexlEngine engine = new Engine();
            JexlContext context = new MapContext();
            context.set("X", param);
            JexlScript script = engine.createScript(writeFormula);
            double result = Double.parseDouble(script.execute(context).toString());
            if (Objects.isNull(variable.getDecimal())) {
                param = Double.parseDouble(String.format("%." + variable.getDecimal() + "f", (result)));
            } else {
                param = Double.parseDouble(String.format("%." + 4 + "f", (result)));
            }
        }
        String messageId = IdUtil.simpleUUID();
        String redisKey = CacheConstants.DEVICE_CONTROL_KEY + messageId;
        redisService.setCacheObject(redisKey, variable.getAddress(), 2L, TimeUnit.MINUTES);
        // 适配硬件的问题，messageId在硬件返回的时候可能会拼接从机地址 cd0a88d990884ab09d0d1201ab99442aslave1
        String redisKey2 = CacheConstants.DEVICE_CONTROL_KEY + messageId + device.getSlaveAddress();
        redisService.setCacheObject(redisKey2, variable.getAddress(), 2L, TimeUnit.MINUTES);
        // 下发控制命令
        sendWriteEmq(device, gateway, variable, param, messageId, redisKey, redisKey2);
        // 下发获取数据命令
        sendReadEmq(gateway.getTopic());
        // 复位变量
        if (NumberConstant.TWO_STR.equals(variable.getReset())
                && Objects.nonNull(variable.getResetTime())
                && StrUtil.isNotEmpty(variable.getResetCommand())) {
            RestControlDTO restControlDTO = new RestControlDTO();
            restControlDTO.setDeviceId(device.getSlaveAddress());
            restControlDTO.setTopic(gateway.getTopic());
            restControlDTO.setAddress(variable.getAddress());
            restControlDTO.setParam(variable.getResetCommand());
            redisService.setCacheObject(CacheConstants.EXPIRE_DEVICE_RESET_CONTROL_KEY + messageId, restControlDTO,
                    Long.parseLong(variable.getResetTime().toString()), TimeUnit.SECONDS);
            redisService.setCacheObject(CacheConstants.DEVICE_RESET_CONTROL_KEY + messageId, restControlDTO);
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(Device device, List<Long> groupIds) {
        Long userId = LoginHelper.getLoginUserId();
        Long deviceId = device.getId();
        device.setNotes(null);
        baseMapper.insert(device);
        // 设备权限
        deviceRoleService.saveDeviceRole(deviceRoleService.initDeviceRole(deviceId));
        // 场景关联设备
        if (CollUtil.isNotEmpty(groupIds)) {
            List<GroupDevice> addList = CollUtil.newArrayList();
            groupIds.forEach(it -> {
                GroupDevice groupDevice = groupDeviceMapper.getLastByOrderDesc(it, userId);
                int sort = 1;
                if (Objects.nonNull(groupDevice) && groupDevice.getSort()!= null){
                    sort += groupDevice.getSort();
                }
                addList.add(new GroupDevice(it, deviceId, userId, sort));
            });
            groupDeviceMapper.batchInsert(addList);
        }
    }

    @Override
    public List<DeviceDTO> selectDeviceList(DeviceDTO param) {
        return baseMapper.selectDeviceList(param);
    }

    /**
     * 下发获取数据
     *
     * @param topic
     */
    private void sendReadEmq(String topic) {
        JSONObject parameter = new JSONObject();
        parameter.set("messageId", IdUtil.simpleUUID());
        parameter.set("devices", "all");
        parameter.set("mode", "2");
        MqttApiMessageDTO message = new MqttApiMessageDTO();
        String prefix = topic.substring(0, topic.lastIndexOf("/") + 1);
        topic = prefix + MqttConstants.VAR_READ;
        message.setTopic(topic);
        message.setPayload(parameter.toString());
        sendEmq(message);
    }

    /**
     * 发送控制信息
     */
    private void sendEmq(MqttApiMessageDTO message) {
        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
        Request request = new Request.Builder()
                .url(url)
                .header("Content-Type", "application/json")
                .header("Authorization", Credentials.basic(username, password))
                .post(RequestBody.Companion.create(new JSONObject(message).toString(), mediaType))
                .build();
        try {
            client.newCall(request).execute();
        } catch (Exception e) {
            throw new ServiceException("发送失败");
        }
    }

    /**
     * 控制命令下发
     *
     * @param device    设备信息
     * @param gateway    网关信息
     * @param variable  变量信息
     * @param param     发送值
     * @param messageId 消息ID
     * @param redisKey  缓存key
     * @param redisKey2 缓存key2
     */
    private void sendWriteEmq(Device device, Gateway gateway, Variable variable, double param, String messageId,
                              String redisKey, String redisKey2) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.set("deviceId", device.getSlaveAddress());
        jsonObject.set("deviceData", new JSONObject().set(variable.getAddress(), param));

        JSONArray jsonArray = new JSONArray();
        jsonArray.add(jsonObject);

        JSONObject parameter = new JSONObject();
        parameter.set("messageId", messageId);
        parameter.set("devices", jsonArray);
        MqttApiMessageDTO message = new MqttApiMessageDTO();
        String prefix = gateway.getTopic().substring(0, gateway.getTopic().lastIndexOf("/") + 1);
        String topic = prefix + MqttConstants.VAR_WRITE;
        message.setTopic(topic);
        message.setPayload(parameter.toString());
        sendEmq(message);
        boolean result = false;
        long startTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - startTime < TimeUnit.SECONDS.toMillis(NumberConstant.TEN)) {
            ThreadUtil.sleep(1000);
            String value = redisService.getCacheObject(redisKey);
            if (MqttConstants.SUCCESS.equals(value)) {
                result = true;
                break;
            }
            String value2 = redisService.getCacheObject(redisKey2);
            if (MqttConstants.SUCCESS.equals(value2)) {
                result = true;
                break;
            }
        }
        if (!result) {
            throw new ServiceLogException("控制失败", LogConstants.CONTROL + device.getDeviceName()
                    + "-" + variable.getName() + "：" + param);
        }
    }

    public void sendResetWriteEmq(String cacheId) {
        try {
            if (StrUtil.isEmpty(cacheId)) {
                return;
            }
            String cacheKey = CacheConstants.DEVICE_RESET_CONTROL_KEY + cacheId;
            RestControlDTO dto = redisService.getCacheObject(cacheKey);
            if (Objects.isNull(dto)) {
                return;
            }
            String deviceId = dto.getDeviceId();
            String topic = dto.getTopic();
            String address = dto.getAddress();
            String param = dto.getParam();
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("deviceId", deviceId);
            jsonObject.set("deviceData", new JSONObject().set(address, param));

            JSONArray jsonArray = new JSONArray();
            jsonArray.add(jsonObject);

            JSONObject parameter = new JSONObject();
            parameter.set("messageId", IdUtil.simpleUUID());
            parameter.set("devices", jsonArray);
            MqttApiMessageDTO message = new MqttApiMessageDTO();
            topic = topic.substring(0, topic.lastIndexOf("/") + 1) + MqttConstants.VAR_WRITE;
            message.setTopic(topic);
            message.setPayload(parameter.toString());
            sendEmq(message);
            redisService.deleteObject(cacheKey);
        } catch (Exception e) {
            log.error("下发重置命令失败：{}", e.getMessage());
        } finally {
            redisService.deleteObject(cacheId);
        }
    }

}
